#include "StdAfx.h"
#include "HeightMap.h"

using namespace std;

namespace FEngine
{
	float sample3x3(int _x, int _z, int _sizeX, int _sizeZ, float* heightMap);

	HeightMap::HeightMap(int _sizeX, int _sizeZ, int _maxHeight, const string& _fileName)
		: Node(), mSizeX(_sizeX), mSizeZ(_sizeZ), mMaxHeight(_maxHeight)
	{
		// Load the Heigh map data
		_loadRAW(_fileName);

		// Create the vertex grid
		FE_VERTEX* vertices = new FE_VERTEX[mSizeX * mSizeZ];
		float color;
		for (int z = 0; z < mSizeZ; ++z)
		{
			for (int x = 0; x < mSizeX; ++x)
			{
				vertices[x + z*mSizeX].POSITION = Vector3((float)x, mMapHeightArray[x + z*mSizeX], (float)z);
				color = ((mMapHeightArray[x + z*mSizeX] / mMaxHeight * 235.0f) + 10) / 255.0f;
				vertices[x + z*mSizeX].NORMAL = Vector3(color, color, color);		// Use the normal as the color data
			}
		}
		
		mNumberVertices = mSizeX * mSizeZ;
		mNumberPrimitives = (mSizeX - 1) * (mSizeZ - 1) * 2;
		
		// Caculate the index
		int* indexes = new int[6 * (mSizeX - 1) * (mSizeZ - 1)];
		int index = 0;
		for (int n = 0; n < (mSizeX * mSizeZ); ++n)
		{
			if ((n % mSizeX) == mSizeX - 1)
				continue;

			if ((n / mSizeX) == mSizeZ - 1)
				break;
		
			indexes[6*index] = n;
			indexes[6*index + 1] = n + mSizeX;
			indexes[6*index + 2] = n + 1;
		
			indexes[6*index + 3] = n + mSizeX;
			indexes[6*index + 4] = n + mSizeX + 1;
			indexes[6*index + 5] = n + 1;

			index += 1;
		}

		// Create a vertex buffer
		int VBSize = mNumberVertices * sizeof(FE_VERTEX);
		CoreEngine::getInstance()->getDeviceInterface()->CreateVertexBuffer(VBSize, D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &mVertexBuffer, NULL);

		// Create an index buffer
		int IBSize = mNumberPrimitives * 3 * sizeof(int);
		CoreEngine::getInstance()->getDeviceInterface()->CreateIndexBuffer(IBSize, D3DUSAGE_WRITEONLY, D3DFMT_INDEX32, D3DPOOL_MANAGED, &mIndexBuffer, NULL);

		VOID* pVoid;

		// Copy the vertex buffer data into memory
		mVertexBuffer->Lock(0, 0, (void**)&pVoid, 0);
		memcpy(pVoid, vertices, VBSize);
		mVertexBuffer->Unlock();
		
		// Copy the index buffer data into memory
		mIndexBuffer->Lock(0, 0, (void**)&pVoid, 0);
		memcpy(pVoid, indexes, IBSize);
		mIndexBuffer->Unlock();
		
		// Load the vertex declaration
		CoreEngine::getInstance()->getDeviceInterface()->CreateVertexDeclaration(VertexDeclaration, &mVertexDeclaration);
	}

	HeightMap::~HeightMap()
	{
		if (mVertexBuffer)
			mVertexBuffer->Release();
		if (mVertexDeclaration)
			mVertexDeclaration->Release();
		if (mIndexBuffer)
			mIndexBuffer->Release();
	}
	
	void HeightMap::update(double _elapsedTime)
	{
		Node::update(_elapsedTime);
	}

	void HeightMap::render(double _elapsedTime)
	{
		Node::render(_elapsedTime);

		LPD3DXEFFECT shader = CoreEngine::getInstance()->getShaderEffect();
		IDirect3DDevice9* device = CoreEngine::getInstance()->getDeviceInterface();

		D3DXMATRIX spaceMat = _getWorldMatrix();
		D3DXMATRIX matWVP = spaceMat * CoreEngine::getInstance()->getCamera()->_getMatrixView() * CoreEngine::getInstance()->getCamera()->_getMatrixProjection();

		if (shader->SetTechnique(shader->GetTechniqueByName("HeightMap")) != D3D_OK)
			throw FEngineException("HeightMap", "render", "Unknown shader technique");
		if (shader->SetMatrix(shader->GetParameterByName(NULL, "gWorldViewProj"), &matWVP) != D3D_OK)
			throw FEngineException("HeightMap", "render", "Unknown shader parameter");
		shader->CommitChanges();
		
		device->SetVertexDeclaration(mVertexDeclaration);
		device->SetStreamSource(0, mVertexBuffer, 0, sizeof(FE_VERTEX));
		device->SetIndices(mIndexBuffer);
		
		unsigned int cPasses, iPass;
		shader->Begin(&cPasses, 0);
		for (iPass= 0; iPass< cPasses; ++iPass)
		{
			shader->BeginPass(iPass);
			CoreEngine::getInstance()->getDeviceInterface()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, mNumberVertices, 0, mNumberPrimitives);
			shader->EndPass();
		}
		shader->End();
	}
	
	int HeightMap::getSizeX() const
	{
		return mSizeX;
	}

	int HeightMap::getSizeZ() const
	{
		return mSizeZ;
	}

	Vector4 HeightMap::getBounds() const
	{
		return Vector4(0.0f, 0.0f, (float)mSizeX, (float)mSizeZ);
	}
	
	float HeightMap::getHeight(const Vector2& _point) const
	{
		return getHeight(_point.x, _point.y);
	}

	float HeightMap::getHeight(float _x, float _z) const
	{
		if (_x < 0 || _x > mSizeX ||
			_z < 0 || _z > mSizeZ)
			return 0.0f;

		// Get the cell we are in
		int col = (int)(_x / 1);
		if (col == mSizeX)
			col -= 1;
		int row = (int)(_z / 1);
		if (row == mSizeZ)
			row -= 1;
		
		// Get the 4 height of the cell's vertices
		// C - D
		// |   |
		// A - B
		float A = mMapHeightArray[col + row * mSizeZ];
		float B = mMapHeightArray[col + 1 + row * mSizeZ];
		float C = mMapHeightArray[col + (row + 1) * mSizeZ];
		float D = mMapHeightArray[col + 1 + (row + 1) * mSizeZ];

		// Check if we are in the ABC or the BCD triangle
		// C - D
		// |   |
		// A - B
		float squaredDistToABC = (_x - col) * (_x - col) + (_z - row) * (_z - row);
		float squaredDistToBCD = (1.0f - _x - col) * (1.0f - _x - col) + (1.0f - _z - row) * (1.0f - _z - row);
		
		// Triangle ABC
		if (squaredDistToABC < squaredDistToBCD)
		{
			float valB = (_x - col) * (B - A);
			float valC = (_z - row) * (C - A);
			return A + valB + valC;
		}
		// Triangle BCD
		else
		{
			float valC = (_x - col) * (C - D);
			float valB = (_z - row) * (B - D);
			return D + valB + valC;
		}

		return 0.0f;
	}
	
	void HeightMap::_loadRAW(const std::string& _fileName)
	{
		FILE *file;
		fopen_s(&file, _fileName.c_str (), "rb");

		if (!file)
			throw FEngineException("HeightMap", "loadRAW", "Cannot load the RAW file");
		
		float* originalData;
		unsigned short fileX, fileZ;
		fread(&fileX, sizeof(unsigned short), 1, file);
		fread(&fileZ, sizeof(unsigned short), 1, file);
		int originalSize = fileX * fileZ;
		originalData = new float[originalSize];

		unsigned char *tmp = new unsigned char[originalSize];

		fread(tmp, sizeof(unsigned char), originalSize, file);
		fclose(file);

		// Get the file's data
		int i = 0;
		for (unsigned short z = 0; z < fileZ; ++z)
			for (unsigned short x = 0; x < fileX; ++x, ++i)
				originalData[i] = float ((mMaxHeight * tmp[i]) / 255.0f);
		
		int size = mSizeX * mSizeZ;
		float* originalScaledData = new float[size];
		
		// Scale the data from the file to fit the map size
		i = 0;
		int posX, posZ;
		float ratioX = (float)fileX / (float)mSizeX;
		float ratioZ = (float)fileZ / (float)mSizeZ;
		for (unsigned short z = 0; z < mSizeZ; ++z)
		{
			for (unsigned short x = 0; x < mSizeX; ++x, ++i)
			{
				posX = (int)(x * ratioX);
				posZ = (int)(z * ratioZ);
				originalScaledData[i] = originalData[posX + posZ * fileX];
			}
		}
		
		// Smooth the map - cube box filter
		mMapHeightArray = new float[size];
		i = 0;
		for (unsigned short z = 0; z < mSizeZ; ++z)
			for (unsigned short x = 0; x < mSizeX; ++x, ++i)
				mMapHeightArray[i] = sample3x3(x, z, mSizeX, mSizeZ, originalScaledData);
				
		delete[] tmp;
		delete[] originalData;
		delete[] originalScaledData;
	}

	// Return the average value of one point in the heightmap
	float sample3x3(int _x, int _z, int _sizeX, int _sizeZ, float* heightMap)
	{
		float avg = 0.0f;
		float num = 0.0f;

		for (int pX = _x - 1; pX <= _x + 1; ++pX)
		{
			for (int pZ = _z - 1; pZ <= _z + 1; ++pZ)
			{
				if (pX < 0 || pX >= _sizeX ||
					pZ < 0 || pZ >= _sizeZ)
					continue;

				avg += heightMap[pX + pZ * _sizeX];
				++num;
			}
		}

		return avg / num;
	}

} // namespace FEngine
